/*
 * Copyright (C) 2015 MediaTek Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 */

#include <linux/delay.h>
#include <linux/uaccess.h>
#include <linux/string.h>


#include "extd_log.h"
#include "extd_factory.h"
#include "extd_info.h"
#include "external_display.h"

#include "dpi_dvt_test.h"

#if defined(CONFIG_MTK_HDMI_SUPPORT)
static struct HDMI_DRIVER *hdmi_tx_drv;
static int is_context_inited;

struct disp_ddp_path_config hdmi_factory_dpi_params;
struct DPI_PARAM_CONTEXT DPI_Params_Context;

struct _hdmi_factory_context {
	bool hdmi_factory_inited;
	bool hdmi_callback_returned;
};

static struct _hdmi_factory_context *_get_context(void)
{
	static struct _hdmi_factory_context g_context;

	if (!is_context_inited) {
		memset((void *)&g_context, 0,
		       sizeof(struct _hdmi_factory_context));
		is_context_inited = 1;
		EXTDINFO("[hdmi]%s set is_context_inited\n", __func__);
	}

	return &g_context;
}

#define pgc	_get_context()

static void hdmi_factory_callback(enum HDMI_STATE state)
{
	EXTDINFO("[hdmi]%s, state: %d\n", __func__, state);
	pgc->hdmi_callback_returned = state;
}

int hdmi_factory_mode_init(void)
{
	EXTDFUNC();

	hdmi_tx_drv = (struct HDMI_DRIVER *) HDMI_GetDriver();
	if (hdmi_tx_drv == NULL) {
		EXTDERR
			("[hdmi]%s, hdmi_init fail, can't get hdmi driver\n",
			__func__);
		return -1;
	}
	hdmi_tx_drv->register_callback(hdmi_factory_callback);

	pgc->hdmi_factory_inited = true;
	return 0;
}

void hdmi_factory_dpi_parameters(int arg, int io_driving)
{
	enum HDMI_POLARITY clk_pol = HDMI_POLARITY_RISING, de_pol =
	    HDMI_POLARITY_RISING, hsync_pol = HDMI_POLARITY_RISING, vsync_pol =
	    HDMI_POLARITY_RISING;
	unsigned int dpi_clock = 0;
	unsigned int dpi_clk_div = 0, hsync_pulse_width = 0, hsync_back_porch =
	    0, hsync_front_porch = 0;
	unsigned int vsync_pulse_width = 0, vsync_back_porch =
	    0, vsync_front_porch = 0;

	switch (arg) {
	case HDMI_VIDEO_720x480p_60Hz:
		{
			clk_pol = HDMI_POLARITY_FALLING;
			de_pol = HDMI_POLARITY_RISING;
			hsync_pol = HDMI_POLARITY_RISING;
			vsync_pol = HDMI_POLARITY_RISING;

			dpi_clk_div = 2;

			hsync_pulse_width = 62;
			hsync_back_porch = 60;
			hsync_front_porch = 16;

			vsync_pulse_width = 6;
			vsync_back_porch = 30;
			vsync_front_porch = 9;

			DPI_Params_Context.bg_height =
			    ((480 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.bg_width =
			    ((720 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.hdmi_width =
			    720 - DPI_Params_Context.bg_width;
			DPI_Params_Context.hdmi_height =
			    480 - DPI_Params_Context.bg_height;
			DPI_Params_Context.output_video_resolution =
			    HDMI_VIDEO_720x480p_60Hz;
			dpi_clock = HDMI_VIDEO_720x480p_60Hz;
			break;
		}
	case HDMI_VIDEO_1280x720p_60Hz:
		{
			if (strncmp(CONFIG_CUSTOM_KERNEL_HDMI, "ANX7625", 7) ==
			    0)
				clk_pol = HDMI_POLARITY_RISING;
			else
				clk_pol = HDMI_POLARITY_FALLING;
			de_pol = HDMI_POLARITY_RISING;
			hsync_pol = HDMI_POLARITY_FALLING;
			vsync_pol = HDMI_POLARITY_FALLING;

			dpi_clk_div = 2;

			hsync_pulse_width = 40;
			hsync_back_porch = 220;
			hsync_front_porch = 110;

			vsync_pulse_width = 5;
			vsync_back_porch = 20;
			vsync_front_porch = 5;
			dpi_clock = HDMI_VIDEO_1280x720p_60Hz;

			DPI_Params_Context.bg_height =
			    ((720 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.bg_width =
			    ((1280 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.hdmi_width =
			    1280 - DPI_Params_Context.bg_width;
			DPI_Params_Context.hdmi_height =
			    720 - DPI_Params_Context.bg_height;

			DPI_Params_Context.output_video_resolution =
			    HDMI_VIDEO_1280x720p_60Hz;
			break;
		}
	case HDMI_VIDEO_1920x1080p_30Hz:
		{
			clk_pol = HDMI_POLARITY_FALLING;
			de_pol = HDMI_POLARITY_RISING;
			hsync_pol = HDMI_POLARITY_FALLING;
			vsync_pol = HDMI_POLARITY_FALLING;

			dpi_clk_div = 2;

			hsync_pulse_width = 44;
			hsync_back_porch = 148;
			hsync_front_porch = 88;

			vsync_pulse_width = 5;
			vsync_back_porch = 36;
			vsync_front_porch = 4;

			DPI_Params_Context.bg_height =
			    ((1080 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.bg_width =
			    ((1920 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.hdmi_width =
			    1920 - DPI_Params_Context.bg_width;
			DPI_Params_Context.hdmi_height =
			    1080 - DPI_Params_Context.bg_height;

			DPI_Params_Context.output_video_resolution =
			    HDMI_VIDEO_1920x1080p_30Hz;
			dpi_clock = HDMI_VIDEO_1920x1080p_30Hz;
			break;
		}
	case HDMI_VIDEO_1920x1080p_60Hz:
		{
			clk_pol = HDMI_POLARITY_FALLING;
			de_pol = HDMI_POLARITY_RISING;
			hsync_pol = HDMI_POLARITY_FALLING;
			vsync_pol = HDMI_POLARITY_FALLING;

			dpi_clk_div = 2;

			hsync_pulse_width = 44;
			hsync_back_porch = 148;
			hsync_front_porch = 88;

			vsync_pulse_width = 5;
			vsync_back_porch = 36;
			vsync_front_porch = 4;

			DPI_Params_Context.bg_height =
			    ((1080 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.bg_width =
			    ((1920 * DPI_Params_Context.scaling_factor) /
			     100 >> 2) << 2;
			DPI_Params_Context.hdmi_width =
			    1920 - DPI_Params_Context.bg_width;
			DPI_Params_Context.hdmi_height =
			    1080 - DPI_Params_Context.bg_height;

			DPI_Params_Context.output_video_resolution =
			    HDMI_VIDEO_1920x1080p_60Hz;
			dpi_clock = HDMI_VIDEO_1920x1080p_60Hz;
			break;
		}

	default:
		break;
	}

	hdmi_factory_dpi_params.dispif_config.dpi.width =
	    DPI_Params_Context.hdmi_width;
	hdmi_factory_dpi_params.dispif_config.dpi.height =
	    DPI_Params_Context.hdmi_height;
	hdmi_factory_dpi_params.dispif_config.dpi.bg_width =
	    DPI_Params_Context.bg_width;
	hdmi_factory_dpi_params.dispif_config.dpi.bg_height =
	    DPI_Params_Context.bg_height;

	hdmi_factory_dpi_params.dispif_config.dpi.clk_pol = clk_pol;
	hdmi_factory_dpi_params.dispif_config.dpi.de_pol = de_pol;
	hdmi_factory_dpi_params.dispif_config.dpi.vsync_pol = vsync_pol;
	hdmi_factory_dpi_params.dispif_config.dpi.hsync_pol = hsync_pol;

	hdmi_factory_dpi_params.dispif_config.dpi.hsync_pulse_width =
	    hsync_pulse_width;
	hdmi_factory_dpi_params.dispif_config.dpi.hsync_back_porch =
	    hsync_back_porch;
	hdmi_factory_dpi_params.dispif_config.dpi.hsync_front_porch =
	    hsync_front_porch;
	hdmi_factory_dpi_params.dispif_config.dpi.vsync_pulse_width =
	    vsync_pulse_width;
	hdmi_factory_dpi_params.dispif_config.dpi.vsync_back_porch =
	    vsync_back_porch;
	hdmi_factory_dpi_params.dispif_config.dpi.vsync_front_porch =
	    vsync_front_porch;

	hdmi_factory_dpi_params.dispif_config.dpi.format = 0;
	hdmi_factory_dpi_params.dispif_config.dpi.rgb_order = 0;
	hdmi_factory_dpi_params.dispif_config.dpi.i2x_en = true;
	hdmi_factory_dpi_params.dispif_config.dpi.i2x_edge = 2;
	hdmi_factory_dpi_params.dispif_config.dpi.embsync = false;
	hdmi_factory_dpi_params.dispif_config.dpi.io_driving_current =
	    (LCM_DRIVING_CURRENT) io_driving;
	hdmi_factory_dpi_params.dispif_config.dpi.dpi_clock = dpi_clock;

	EXTDMSG("[hdmi]%s:%d\n", __func__, arg);
}

int hdmi_factory_mode_test(enum HDMI_FACTORY_TEST test_step, void *info)
{
	int ret = 0;

	if (pgc->hdmi_factory_inited == false)
		hdmi_factory_mode_init();

	switch (test_step) {
	case STEP1_CHIP_INIT:
		{
			EXTDMSG("[hdmi] STEP1_CHIP_INIT\n");
			hdmi_tx_drv->power_on();
			hdmi_tx_drv->audio_enable(1);
			break;
		}
	case STEP2_JUDGE_CALLBACK:
		{
			int hdmi_status = (int)pgc->hdmi_callback_returned;

			EXTDMSG("[hdmi] STEP2_JUDGE_CALLBACK: %d\n",
					 pgc->hdmi_callback_returned);
			if (copy_to_user
			    (info, &hdmi_status, sizeof(hdmi_status))) {
				EXTDERR
				    ("[HDMI]copy_to_user failed! line:%d\n",
				     __LINE__);
				ret = -1;
			}
			break;
		}
	case STEP3_START_DPI_AND_CONFIG:
		{
			/*
			 *  test_type(24bit-31bit), resolution/
			 *  test_case(16bit-23bit),
			 *	hsync/vsync/de/clk io drivint(8bit-15bit),
			 *  data io driving(0bit-7bit)
			 *  test_type (factory:0, HQA:1, DVT:2)
			 */
			int test_type = ((long int)info >> 24);
			int resolution = (((long int)info >> 16) & 0xFF);
			int test_case = resolution;
			int io_driving = ((long int)info & 0xFFFF);

			EXTDMSG("STEP3_START_DPI_AND_CONFIG +\n");
			EXTDMSG
			    ("resolution/test case:%d, driving:0x%x\n",
			     resolution, io_driving);

			if (test_type == 0) {	/* Factory mode */
				hdmi_factory_dpi_parameters(resolution,
							    io_driving);
				ext_disp_factory_test(0,
						      (void *)
						      &hdmi_factory_dpi_params);

				msleep(100);
				hdmi_tx_drv->video_config(resolution,
					HDMI_VIN_FORMAT_RGB888,
					HDMI_VOUT_FORMAT_RGB888);
			} else if (test_type == 1) {	/* HQA */
				hdmi_factory_dpi_parameters(resolution,
							    io_driving);
				ext_disp_factory_test(0,
						      (void *)
						      &hdmi_factory_dpi_params);

				EXTDMSG
				    ("[hdmi] Not need video config\n");
			} else if (test_type == 2) {	/* DVT */
				EXTDMSG("[hdmi] Start DPI DVT Test\n");
				dpi_dvt_ioctl(test_case);
			}

			break;
		}
	case STEP4_DPI_STOP_AND_POWER_OFF:
		{
			int test_type = ((long int)info >> 24);

			EXTDMSG("[hdmi] STEP4_DPI_STOP_AND_POWER_OFF\n");
			hdmi_tx_drv->power_off();
			if (test_type == 0)
				ext_disp_factory_test(1,
						      (void *)
						      &hdmi_factory_dpi_params);

			pgc->hdmi_factory_inited = false;
			is_context_inited = false;

			hdmi_tx_drv->unregister_callback(hdmi_factory_callback);
			break;
		}
	default:
		break;
	}

	return ret;
}
#endif				/* CONFIG_MTK_HDMI_SUPPORT */

const struct EXTD_DRIVER *EXTD_Factory_HDMI_Driver(void)
{
	static const struct EXTD_DRIVER extd_factory_hdmi = {
#if defined(CONFIG_MTK_HDMI_SUPPORT)
		.init = hdmi_factory_mode_init,
		.deinit = NULL,
		.enable = NULL,
		.power_enable = NULL,
		.set_audio_enable = NULL,
		.set_resolution = NULL,
		.get_dev_info = NULL,
		.get_capability = NULL,
		.get_edid = NULL,
		.wait_vsync = NULL,
		.fake_connect = NULL,
		.factory_mode_test = hdmi_factory_mode_test,
		.ioctl = NULL
#else
		.init = 0
#endif
	};

	return &extd_factory_hdmi;
}

const struct EXTD_DRIVER *EXTD_Factory_EPD_Driver(void)
{
	static const struct EXTD_DRIVER extd_factory_epd = {
#if defined(CONFIG_MTK_EPD_SUPPORT)
		.init = NULL,
		.deinit = NULL,
		.enable = NULL,
		.power_enable = NULL,
		.set_audio_enable = NULL,
		.set_resolution = NULL,
		.get_dev_info = NULL,
		.get_capability = NULL,
		.get_edid = NULL,
		.wait_vsync = NULL,
		.fake_connect = NULL,
		.factory_mode_test = epd_factory_mode_test,
		.ioctl = NULL
#else
		.init = 0
#endif
	};

	return &extd_factory_epd;
}
